home *** CD-ROM | disk | FTP | other *** search
/ GameStar 2004 April / Gamestar_61_2004-04_dvdb.iso / DVDStar / Editace / hltp.exe / {app} / Applications / QuArK / quarkpy / mapbezier.py < prev    next >
Text File  |  2004-01-05  |  34KB  |  978 lines

  1. """   QuArK  -  Quake Army KnifeManagement of Bezier patches
  2.  
  3.  
  4. """
  5. #
  6. # Copyright (C) 1996-99 Armin Rigo
  7. # THIS FILE IS PROTECTED BY THE GNU GENERAL PUBLIC LICENCE
  8. # FOUND IN FILE "COPYING.TXT"
  9. #
  10.  
  11. #$Header: /cvsroot/quark/runtime/quarkpy/mapbezier.py,v 1.36 2001/06/17 21:05:27 tiglari Exp $
  12.  
  13.  
  14. import quarkx
  15. from maputils import *
  16. import qhandles
  17. import maphandles
  18. import mapentities
  19. import dlgclasses
  20.  
  21. from plugins.tagging import *
  22. import plugins.maptagpoint
  23. from b2utils import *
  24.  
  25. class CornerTexPos(dlgclasses.LiveEditDlg):
  26.     endcolor = AQUA
  27.     size = (170,190)
  28.     dfsep = 0.4
  29.  
  30.     dlgdef = """
  31.         {
  32.         Style = "9"
  33.         Caption = "Texture Corner Dialog"
  34.  
  35.  
  36.         Corners: =
  37.         {
  38.          Txt = "texrect" Typ = "EF004"
  39.          Hint = "(0,0).s (0,0).t, (m,n).s, (m,n).t, when tex placement is rectangular." $0D "Set these numbers to enforce a rectangular texture scale." $0D "If this is blank, the scale is not rectangular"
  40.         }
  41.         Corner1: =
  42.         {
  43.          Txt = "(0,0)"  Typ = "EF002"
  44.          Hint = "s t texture coordinates for 0,0 corner"
  45.         }
  46.         Corner2: =
  47.         {
  48.          Txt = "(0,n)"  Typ = "EF002"
  49.          Hint = "s t texture coordinates for 0,n corner"
  50.         }
  51.         Corner3: =
  52.         {
  53.          Txt = "(m,0)"  Typ = "EF002"
  54.          Hint = "s t texture coordinates for m,0 corner"
  55.         }
  56.         Corner4: =
  57.         {
  58.          Txt = "(m,n)"  Typ = "EF002"
  59.          Hint = "s t texture coordinates for m,n corner"
  60.         }
  61.  
  62.         sep: = { Typ="S" Txt=""}
  63.  
  64.         fixed: ={Txt="fixed int." Typ="X"
  65.                 }
  66.                 
  67.         sep: = { Typ="S" Txt=""}
  68.  
  69.  
  70.  
  71.         exit:py = { Txt="" }
  72.     }
  73.     """
  74.  
  75. class CPTexPos(dlgclasses.LiveEditDlg):
  76.     endcolor = AQUA
  77.     size = (100,100)
  78.     dfsep = 0.5
  79.  
  80.     dlgdef = """
  81.         {
  82.         Style = "9"
  83.         Caption = "Positioning Dialog"
  84.  
  85.         Coords: = 
  86.         {
  87.         Txt = "&"
  88.         Typ = "EF002"
  89.         Hint = "s t texture coordinates.  Enter new ones here." $0D "The difference between new and old can be propagated to row, column or all with checkboxes below."
  90.         }
  91.  
  92.         sep: = {Typ="S" Txt=" "} 
  93.         moverow: ={Txt="move row" Typ="X"
  94.                    Hint = "If this is checked, texture movement applies to whole row (same color)."}
  95.         movecol: ={Txt="move col" Typ="X"
  96.                    Hint = "If this is checked, texture movement applies to whole column (different colors)."}
  97.         moveall: ={Txt="move all" Typ="X"
  98.                    Hint = "If this is checked, whole texture is shifted"}
  99.         
  100.         sep: = { Typ="S" Txt=""}
  101.  
  102.         exit:py = {Txt="" }
  103.     }
  104.     """
  105.  
  106. def pointsToMove(moverow, movecol, i, j, h, w):
  107.     "returns list of (i,j) indexes of points to move"
  108.     "if moverow==1 and movecol==1, move everything"
  109.     if moverow and movecol:
  110.         def row(i,w=w):
  111.              return map(lambda j,i=i:(i,j),range(w))
  112.         return reduce(lambda x,y:x+y, map(row,range(h))) 
  113.     if movecol:
  114.         return map(lambda i,j=j:(i, j),range(h))
  115.     if moverow:
  116.         return map(lambda j,i=i:(i, j), range(w))
  117.     return (i, j),  # Newbie Pythonistas: the comma is not a typo,
  118.                     # but means that the function returns a 
  119.                     # 1-element tuple whose sole element is a 2-tuple.
  120.  
  121.  
  122. def texcpclick(m):
  123.     h, editor = m.h, m.editor
  124.           
  125.     class pack:
  126.         "place to stick stuff"
  127.     pack.ij, pack.b2 = h.ij, h.b2
  128.  
  129.     def setup(self, pack=pack):
  130.         cp, (i, j), b2 = map(list, pack.b2.cp), pack.ij, pack.b2
  131.         src = self.src
  132.         p = cp[i][j]
  133.         src["Coords"] = cp[i][j].s, cp[i][j].t
  134.  
  135.     def action(self, pack=pack):
  136.         cp, (i, j), b2 = copyCp(pack.b2.cp),   pack.ij, pack.b2
  137.         s, t = self.src["Coords"]
  138.         os, ot = cp[i][j].st
  139.         ds, dt = s-os, t-ot
  140.         delta = quarkx.vect(0, 0, 0, ds, dt)
  141.         moverow, movecol, moveall = self.src["moverow"], self.src["movecol"], self.src["moveall"]
  142.         if moveall:
  143.             moverow=movecol=1
  144.         for (m, n) in pointsToMove(moverow, movecol, i, j, b2.H, b2.W):
  145.             cp[m][n] = cp[m][n]+delta
  146.         new = b2.copy()
  147.         new.cp = cp
  148.         undo=quarkx.action()
  149.         undo.exchange(b2, new)
  150.         self.editor.ok(undo,"move texture")
  151.         pack.b2 = new
  152.         self.editor.invalidateviews()
  153.  
  154.     CPTexPos(quarkx.clickform, 'beztexpos', editor, setup, action)
  155.  
  156.  
  157. #
  158. # The idea here is to use the bezier formulas (see comments to bezier.pas)
  159. #  to compute the 1/4, 1/2, 3/4 points on the bezier curve segment
  160. #  whose midpoint is at i (in each column), the 1/2 point becomes
  161. #  the new even coordinate cp, & the new odd coordinate cp's are
  162. #  chosen to get the line to pass thru the 1/4 and 3/4 points.
  163. #
  164. # The structure of this code should be revamped to operate on
  165. #  ranges of rows, columns or both at once, then thinning should
  166. #  be added.
  167. #
  168. def quilt_addrow(cp,(i,j)):
  169.   "alters cp so that two patch-rows replace the ith one"
  170.   md, q1, q3 = [], [], []
  171.   #
  172.   # Should try to do this with maplist ...
  173.   #
  174.   # & We'll probably want a variant to do this to a whole list
  175.   #
  176.   for c in range(len(cp[0])):
  177.       arc = cp[i-1][c],cp[i][c],cp[i+1][c]
  178.       mid = apply(b2midpoint, arc)
  179.       md.append(mid)
  180.       qt1 = apply(b2qtpoint, arc)
  181.       qt3 = apply(b2qt3point, arc)
  182.       q1.append(b2midcp(cp[i-1][c],qt1, mid))
  183.       q3.append(b2midcp(mid,qt3,cp[i+1][c]))
  184.   cp[i:i+1] = [q1, md, q3]
  185.  
  186.  
  187. def doubleRowsOfQuilt(cp):
  188.     newcp = copyCp(cp)
  189.     for i in range(len(cp)-2,0,-2):
  190.         quilt_addrow(newcp,(i,0))
  191.     return newcp
  192.  
  193. def quilt_addcol(cp,(i,j)):
  194.   "alters cp so that two patch-rows replace the ith one"
  195.   for row in cp:
  196.      arc = row[j-1],row[j],row[j+1]
  197.      mid = apply(b2midpoint, arc)
  198.      qt1 = apply(b2qtpoint, arc)
  199.      qt3 = apply(b2qt3point, arc)
  200.      row[j:j+1]=[b2midcp(arc[0],qt1, mid),
  201.           mid,b2midcp(mid,qt3,arc[2])]
  202.  
  203. def doubleColsOfQuilt(cp):
  204.     newcp = copyCp(cp)
  205.     for j in range(len(cp[0])-2,0,-2):
  206.         quilt_addcol(newcp,(0,j))
  207.     return newcp
  208.  
  209. def quilt_delrow(cp,(i,j)):
  210.     md = []
  211.     for c in range(len(cp[0])):
  212.         arc = cp[i-2][c],cp[i][c],cp[i+2][c]
  213.         mid = apply(b2midcp,arc)
  214.         md.append(mid)
  215.     cp[i-1:i+2]=[md]
  216.     
  217.  
  218. def quilt_delcol(cp, (i,j)):
  219.     for row in cp:
  220.         arc=row[j-2],row[j],row[j+2]
  221.         mid = apply(b2midcp,arc)
  222.         row[j-1:j+2]=[mid]
  223.         
  224.  
  225. #
  226. # Handles for control points.
  227. #
  228.  
  229.  
  230. class CPHandle(qhandles.GenericHandle):
  231.     "Bezier Control point."
  232.  
  233.     undomsg = Strings[627]
  234.     hint = "reshape bezier patch (Ctrl key: force control point to grid)\n  Alt: move whole row (same hue)\n  Shift: move whole column.\n  Shift+Alt key: move everything.  \n S: shift texture instead.||This is one of the control points of the selected Bezier patch. Moving this control points allows you to distort the shape of the patch. Control points can be seen as 'attractors' for the 'sheet of paper' Bezier patch."
  235.  
  236.     def __init__(self, pos, b2, ij, color): #DECKER
  237.         qhandles.GenericHandle.__init__(self, pos)
  238.         self.b2 = b2
  239.         self.ij = ij
  240.         self.hint = "(%s,%s)--"%ij+self.hint
  241.         self.color = color #DECKER
  242.         self.cursor = CR_CROSSH
  243.         self.h = len(b2.cp) 
  244.         self.w =  len(b2.cp[0])
  245.  
  246.     def draw(self, view, cv, draghandle=None):
  247.         if self.ij == (0,0):
  248.             # draw the control point net but only once
  249.             cv.reset()
  250.             self.drawcpnet(view, cv)
  251.         p = view.proj(self.pos)
  252.         if p.visible:
  253.             cv.reset()
  254.             #cv.brushcolor = MapColor("Bezier")
  255.             #cv.rectangle(p.x-3, p.y-3, p.x+4, p.y+4)
  256.             #cv.rectangle(p.x-0.501, p.y-0.501, p.x+2.499, p.y+2.499)
  257.             cv.brushcolor = self.color #DECKER
  258.             cv.rectangle(p.x-3, p.y-3, p.x+4, p.y+4)
  259.  
  260.     #
  261.     # This is important because in general the derivative
  262.     #  will only be well-defined at corners
  263.     #
  264.     def iscorner(self):
  265.         i, j = self.ij
  266.         if not (i==0 or i==self.b2.H-1):
  267.             return 0
  268.         if not (j==0 or j==self.b2.W-1):
  269.             return 0
  270.         return 1
  271.         
  272.     def edgeType(self):
  273.         "(type, dim); type=P_FRONT etc"
  274.         "None; not an edge"
  275.         i, j = self.ij
  276.         cp = self.b2.cp
  277.         h = len(cp)
  278.         w = len(cp[0])
  279.         if 0<i<h-1:
  280.             if j==0:
  281.                 return P_FRONT, h
  282.             if j==w-1:
  283.                 return P_BACK, h 
  284.         if 0<j<w-1:
  285.             if i==0:
  286.                 return P_BOTTOM, w
  287.             if i==h-1:
  288.                 return P_TOP, w            
  289.  
  290.  
  291.     #
  292.     # Things that are only sensible for particular control points
  293.     #  should go here, things that are sensible for the whole patch
  294.     #  should go on the BezierType menu update below.
  295.     #
  296.  
  297.     def menu(self, editor, view):
  298.  
  299.         i, j = self.ij
  300.         cp = self.b2.cp
  301.  
  302.         patchmenu = mapentities.CallManager("menu", self.b2, editor)+self.OriginItems(editor, view)
  303.  
  304.         texcp = qmenu.item("Texture Coordinates",texcpclick)
  305.         texcp.h, texcp.editor = self, editor
  306.         texpop = findlabelled(patchmenu,'texpop')
  307.         texpop.items[:0] = [texcp, qmenu.sep]
  308.  
  309.         def thickenclick(m,self=self,editor=editor):
  310.           new = self.b2.copy()
  311.           #
  312.           # Operating on cp's `in situ' doesn't seem to work.
  313.           #
  314.  
  315.           ncp = copyCp(new.cp)
  316.           m.thicken(ncp, self.ij)
  317.           #
  318.           # this setting of the cp attribute triggers a lot of stuff
  319.           #   in the delphi
  320.           #
  321.           new.cp = ncp
  322.           undo = quarkx.action()
  323.           undo.exchange(self.b2, new)
  324.           editor.ok(undo,"thicken mesh")
  325.  
  326.         def thinclick(m,self=self,editor=editor):
  327.           new = self.b2.copy()
  328.           #
  329.           # Operating on cp's `in situ' doesn't seem to work.
  330.           #
  331.  
  332.           ncp = copyCp(new.cp)
  333.           m.thin(ncp, self.ij)
  334.           #
  335.           # this setting of the cp attribute triggers a lot of stuff
  336.           #   in the delphi
  337.           #
  338.           new.cp = ncp
  339.           undo = quarkx.action()
  340.           undo.exchange(self.b2, new)
  341.           editor.ok(undo,"thin mesh")
  342.  
  343.  
  344.         #
  345.         # We have both add row & column because sometimes an edge
  346.         #  point is hard to find, and for interior points either
  347.         #  would be possible
  348.         #
  349.         addrow = qmenu.item("Add Row",thickenclick,"|Adds a row to the mesh")
  350.         delrow = qmenu.item("Delete Row", thinclick,"|Removes a row from the mesh")
  351.         if iseven(i):
  352.           addrow.state=qmenu.disabled
  353.           delrow.thin=quilt_delrow
  354.         else:
  355.           addrow.thicken=quilt_addrow
  356.           delrow.state=qmenu.disabled
  357.         if len(cp)<4 or i==0 or i==len(cp)-1:
  358.           delrow.state=qmenu.disabled
  359.           
  360.         addcol = qmenu.item("Add Column",thickenclick,"|Adds a column to the mesh")
  361.         delcol = qmenu.item("Delete Column",thinclick,"|Removes a column from the mesh")
  362.         if iseven(j):
  363.           addcol.state=qmenu.disabled
  364.           delcol.thin=quilt_delcol
  365.         else:
  366.           addcol.thicken=quilt_addcol
  367.           delcol.state=qmenu.disabled
  368.         if len(cp[0])<4 or j==0 or j==len(cp[0])-1:
  369.           delcol.state=qmenu.disabled
  370.           
  371.         def meshclick(m, self=self, editor=editor):
  372.             b2 = self.b2
  373.             new = b2.copy()
  374.             new.cp = m.action(new.cp)
  375.             undo = quarkx.action()
  376.             undo.exchange(b2, new)
  377.             editor.ok(undo, "change mesh")
  378.         
  379.         
  380.         doublerow = qmenu.item("Double Rows", meshclick, "|Double the number of rows in the mesh")
  381.         doublerow.action = doubleRowsOfQuilt
  382.         
  383.         doublecol = qmenu.item("Double Columns", meshclick, "|Double the number of columns in the mesh")
  384.         doublecol.action = doubleColsOfQuilt
  385.         
  386.         mesh = qmenu.popup("Mesh",[addrow, addcol, delrow, delcol,
  387.                 qmenu.sep, doublerow, doublecol])
  388.         
  389.         
  390.         tagpt = gettaggedpt(editor)
  391.  
  392.         #
  393.         # This should be slated for removal because `glue to tagged'
  394.         # defined in plugins.maptagpoint already does the job (a bit
  395.         # of that ol' Armin magic...
  396.         #
  397.         def glueclick(m, editor=editor,tagpt=tagpt,b2=self.b2,(i,j)=self.ij):
  398.             cp=copyCp(b2.cp)
  399.             if quarkx.keydown('S'):
  400.               cp[i][j]=tagpt
  401.             else:
  402.               cp[i][j] = quarkx.vect(tagpt.xyz+(cp[i][j]).st)
  403.             new = b2.copy()
  404.             new.cp = cp
  405.             undo=quarkx.action()
  406.             undo.exchange(b2, new)
  407.             editor.ok(undo,"glue to tagged")
  408.             editor.invalidateviews()
  409.    
  410.         glue = qmenu.item("&Glue to tagged point", glueclick)
  411.         if tagpt is None:
  412.             glue.state=qmenu.disabled
  413.         
  414.         def JoinClick(m,self=self, editor=editor):
  415.             b2 = self.b2
  416.             tb2 = m.tagged.b2
  417. #            ncp = map(lambda row1, row2,b2=b2,tb2=tb2:row1+row2[1:],tb2.cp,b2.cp)
  418.             ncp = joinCp(m.tagtype,tb2.cp,m.selftype,b2.cp)
  419.             new =tb2.copy()
  420.             new.cp = ncp
  421.             undo = quarkx.action()
  422.             undo.exchange(tb2, new)
  423.             undo.exchange(b2,None)
  424.             editor.ok(undo,'Join Patches')
  425.             editor.invalidateviews()
  426.  
  427.         def KnitClick(m,self=self, editor=editor):
  428.             b2 = self.b2
  429.             tb2 = m.tagged.b2
  430.             ncp = knitCp(m.selftype,b2.cp,m.tagtype,tb2.cp)
  431.             new = b2.copy()
  432.             new.cp = ncp
  433.             undo = quarkx.action()
  434.             undo.exchange(b2, new)
  435.             editor.ok(undo,'Knit edges')
  436.             editor.invalidateviews()
  437.  
  438.         joinitem = qmenu.item("&Join patch to tagged",JoinClick,"|Combine tagged patch and this one into one quilt")
  439.         knititem = qmenu.item("&Knit edge to tagged",KnitClick,"|Attach this edge to tagged edge")
  440.         joinitem.state=knititem.state=qmenu.disabled
  441.         tagged = gettaggedb2cp(editor)
  442.         for item in joinitem, knititem:
  443.           if tagged is not None:
  444.             tagtype = tagged.edgeType() # orientation, dimension of edge
  445.             selftype = self.edgeType()
  446.             if tagtype is not None and selftype is not None:
  447.                 if tagtype[1]==selftype[1]:  # same dim
  448.                     item.state=qmenu.normal
  449.                     item.tagtype, item.selftype = tagtype, selftype
  450.                     item.tagged = tagged
  451.           if item.state==qmenu.disabled:
  452.              morehint = "\n\nTo enable this menu item, tag a non-corner edge point of one patch, and RMB on a non-corner edge point of another"
  453.              item.hint=item.hint+morehint
  454.  
  455.         tagged = gettaggededge(editor)
  456.         
  457.         def alignclick(m,self=self,tagged=tagged,editor=editor):
  458.             b2 = self.b2.copy()
  459.             cp = b2.cp
  460.             i, j = self.ij
  461.             norm = (tagged[1]-tagged[0]).normalized
  462.             p = cp[i][j]
  463.             #
  464.             # operating on cp's of b2's tends not to work; make a copy
  465.             #
  466.             cp2 = copyCp(cp)
  467.             if m.col:
  468.                 for k in range(len(cp)):
  469.                     delta = perptonormthru(cp[k][j], p, norm)
  470.                     delta = quarkx.vect(delta.xyz+(0.0, 0.0))
  471.                     cp2[k][j] = cp2[k][j]-delta
  472.                     mess = "align column"
  473.             else:
  474.                 for k in range(len(cp[0])):
  475.                     delta = perptonormthru(cp[i][k], p, norm)
  476.                     delta = quarkx.vect(delta.xyz+(0.0, 0.0))
  477.                     cp2[i][k] = cp2[i][k]-delta
  478.                     mess = "align row"
  479.             b2.cp = cp2
  480.             undo_exchange(editor,self.b2,b2,mess)
  481.  
  482.         alignrow = qmenu.item('Align Row to tagged edge',alignclick,"|Aligns the row to paralell to tagged edge, passing thru this point")
  483.         aligncol = qmenu.item('Align Col to tagged edge',alignclick,"|Aligns the column to paralell to tagged edge, passing thru this point")
  484.         if tagged is None:
  485.             alignrow.state=qmenu.disabled
  486.             aligncol.state=qmenu.disabled
  487.         else:
  488.             alignrow.col=0
  489.             aligncol.col=1
  490.  
  491. #
  492. # not sure why this was here, testing perhaps?
  493. #
  494. #        def subdivide(m,self=self,editor=editor):
  495. #            cp = copyCp(self.b2.cp)
  496. #            new = self.b2.copy()
  497. #            newcp = subdivideColumns(3,cp)
  498. #            new.cp = newcp
  499. #            undo=quarkx.action()
  500. #            undo.exchange(self.b2, new)
  501. #            editor.ok(undo,"subdivide")
  502. #
  503. #        subdiv = qmenu.item("subdivide",subdivide)
  504. #        return [texcp, thicken] + [qmenu.sep] + mapentities.CallManager("menu", self.b2, editor)+self.OriginItems(editor, view)
  505.  
  506.         index = i*(self.b2.W)+j
  507.         picked=self.b2["picked"] 
  508.         
  509.         def pickClick(m,editor=editor,b2=self.b2,index=index, picked=picked):
  510.             if picked is None:
  511.                 picked = index,
  512.             else:
  513.                 picked = picked + (index,)
  514.             undo=quarkx.action()
  515.             undo.setspec(b2,"picked",picked)
  516.             editor.ok(undo,"Pick CP")
  517.         
  518.         def unPickClick(m,editor=editor,b2=self.b2,index=index, picked=picked):
  519.             if len(picked)==1:
  520.                 picked=None
  521.             else:
  522.                 loc = list(picked).index(index)
  523.                 picked = picked[:loc]+picked[loc+1:]
  524.             undo=quarkx.action()
  525.             undo.setspec(b2,"picked",picked)
  526.             editor.ok(undo,"Unpick CP")
  527.         
  528.         def unPickAllClick(m,editor=editor,b2=self.b2):
  529.             undo=quarkx.action()
  530.             undo.setspec(b2,"picked",None)
  531.             editor.ok(undo,"Unpick All")
  532.             editor.invalidateviews()
  533.             
  534.         pickItem = qmenu.item("Pick CP", pickClick,"|When one or more CPs are picked `picked', dragging one of them drags all, and movement palette operations applied to a patch are applied only to the picked CPs.\n\nPatches remember which of their CPs are picked.")
  535.         unPickItem = qmenu.item("Unpick CP", unPickClick)
  536.         unPickAllItem = qmenu.item("Unpick All", unPickAllClick)
  537.       
  538.         unPickItem.state=qmenu.disabled
  539.         if picked is not None:
  540.             for ind in picked:
  541.                 if ind==index:
  542.                     pickItem.state=qmenu.disabled
  543.                     unPickItem.state=qmenu.normal
  544.                     break
  545.         else:
  546.             unPickAllItem.state=qmenu.disabled
  547.       
  548.         picklist = [qmenu.sep, pickItem, unPickItem, unPickAllItem]
  549.         
  550.         return [mesh, joinitem, knititem, alignrow, aligncol] + picklist+[qmenu.sep] + patchmenu
  551.     
  552.     def drawcpnet(self, view, cv, cp=None):
  553.         #
  554.         # This function draws the net joining the control points in a selected patch
  555.         #
  556.         if cp is None:
  557.             cp = self.b2.cp
  558.         #
  559.         # Project all control points using view.proj
  560.         #
  561.         cp = map(lambda cpline, proj=view.proj: map(proj, cpline), cp)
  562.         #
  563.         # Draw the horizontal lines
  564.         #
  565.         for cpline in cp:
  566.             for j in range(len(cpline)-1):
  567.                 cv.line(cpline[j], cpline[j+1])
  568.         #
  569.         # Transpose the "cp" matrix and draw the vertical lines
  570.         #
  571.         cp = apply(map, (None,)+tuple(cp))
  572.         for cpline in cp:
  573.             for i in range(len(cpline)-1):
  574.                 cv.line(cpline[i], cpline[i+1])
  575.  
  576.     def drawred(self, redimages, view, redcolor, oldcp=None):
  577.         #
  578.         # Draw a rough net joining all control points while dragging one of them.
  579.         #
  580.         if oldcp is None:
  581.             try:
  582.                 oldcp = self.newcp
  583.             except AttributeError:
  584.                 return
  585.             if oldcp is None:
  586.                 return
  587.         cv = view.canvas()
  588.         cv.pencolor = redcolor
  589.         #
  590.         # Draw the net
  591.         #
  592.         self.drawcpnet(view, cv, oldcp)
  593.         return oldcp
  594.  
  595.     # converting to standard ij
  596.     def drag(self, v1, v2, flags, view):
  597.         delta = v2-v1
  598.         if not (flags&MB_CTRL):
  599.             delta = qhandles.aligntogrid(delta, 0)
  600.         if delta or (flags&MB_REDIMAGE):
  601.             new = self.b2.copy()
  602.             cp = map(list, self.b2.cp)
  603.             i, j = self.ij
  604.             moverow = (quarkx.keydown('\022')==1)  # ALT
  605.             movecol = (quarkx.keydown('\020')==1)  # SHIFT
  606.             picked = self.b2["picked"]
  607.             if picked:
  608.                 indexes = map(lambda p,b2=self.b2:cpPos(p,b2),picked)
  609.             else:
  610.                 indexes = pointsToMove(moverow, movecol, i, j, self.h, self.w)        # tiglari, need to unswap 
  611. #            squawk(`indexes`)
  612.             td = (v2-v1)/128
  613.             for m,n in indexes:
  614.                 p = cp[m][n] + delta
  615.                 if flags&MB_CTRL:
  616.                     p = qhandles.aligntogrid(p, 0)
  617.                 if quarkx.keydown('S')==1: # RMB
  618.                     xaxis, yaxis = tanAxes(cp,i,j)
  619.                     xaxis, yaxis = -xaxis, -yaxis
  620.                     q = cp[m][n]
  621.                     cp[m][n]=quarkx.vect(q.x, q.y, q.z,
  622.                               q.s+td*yaxis, q.t+td*xaxis)
  623.                 else:
  624.                    cp[m][n] = quarkx.vect(p.x, p.y, p.z)  # discards texture coords
  625. #            if 0:
  626.             if quarkx.keydown('S')==1:
  627.                     self.draghint="tex coords: %.2f, %.2f"%(cp[i][j].s, cp[i][j].t)
  628.             else:
  629.                     self.draghint = vtohint(delta)
  630.             if self.b2["smooth"]:
  631.                 # keep the patch smoothness
  632.                 def makesmooth(di,dj,i=i,j=j,cp=cp):
  633.                     p = 2*cp[i+di][j+dj] - cp[i][j]
  634.                     cp[i+di+di][j+dj+dj] = quarkx.vect(p.x, p.y, p.z)  # discards texture coords
  635.                 if j&1:
  636.                     if j>2: makesmooth(0,-1)
  637.                     if j+2<len(cp[0]): makesmooth(0, 1)
  638.                 if i&1:
  639.                     if i>2: makesmooth(-1,0)
  640.                     if i+2<len(cp): makesmooth(1,0)
  641.             new.cp = self.newcp = cp
  642.             new = [new]
  643.         else:
  644.             self.newcp = None
  645.             new = None
  646.         return [self.b2], new
  647.  
  648. class CPTextureHandle(qhandles.GenericHandle):
  649.     "Bezier Control point (Texture Handle)."
  650.  
  651.     undomsg = Strings[627]
  652.     hint = "re-position texture vertexes (Ctrl key: force control point to grid)\n  Alt: move whole row (same hue)\n  Shift: move whole column.\n  Shift+Alt key: move everything.  \n S: shift texture instead.||This is one of the control points of the selected Bezier patch. Moving this control points allows you to distort the shape of the patch. Control points can be seen as 'attractors' for the 'sheet of paper' Bezier patch."
  653.  
  654.     def __init__(self, pos, b2, ij, color): #DECKER
  655.         qhandles.GenericHandle.__init__(self, pos)
  656.         self.b2 = b2
  657.         self.ij = ij
  658.         self.hint = "(%s,%s)--"%ij+self.hint
  659.         self.color = color #DECKER
  660.         self.cursor = CR_CROSSH
  661.         self.h = len(b2.cp) 
  662.         self.w =  len(b2.cp[0])
  663.  
  664.     def draw(self, view, cv, draghandle=None):
  665.         if self.ij == (0,0):
  666.             cv.reset()
  667.             #self.drawcpnet(view, cv)
  668.         p = view.proj(self.pos)
  669.         if p.visible:
  670.             cv.reset()
  671.             cv.brushcolor = self.color #DECKER
  672.             cv.rectangle(p.x-3, p.y-3, p.x+4, p.y+4)
  673.  
  674.     #
  675.     # This is important because in general the derivative
  676.     #  will only be well-defined at corners
  677.     #
  678.     def iscorner(self):
  679.         i, j = self.ij
  680.         if not (i==0 or i==self.b2.H-1):
  681.             return 0
  682.         if not (j==0 or j==self.b2.W-1):
  683.             return 0
  684.         return 1
  685.         
  686.     def edgeType(self):
  687.         "(type, dim); type=P_FRONT etc"
  688.         "None; not an edge"
  689.         i, j = self.ij
  690.         cp = self.b2.cp
  691.         h = len(cp)
  692.         w = len(cp[0])
  693.         if 0<i<h-1:
  694.             if j==0:
  695.                 return P_FRONT, h
  696.             if j==w-1:
  697.                 return P_BACK, h 
  698.         if 0<j<w-1:
  699.             if i==0:
  700.                 return P_BOTTOM, w
  701.             if i==h-1:
  702.                 return P_TOP, w            
  703.     
  704.     # converting to standard ij
  705.     def drag(self, v1, v2, flags, view):
  706.         delta = v2-v1
  707.         if not (flags&MB_CTRL):
  708.             delta = qhandles.aligntogrid(delta, 0)
  709.         if delta or (flags&MB_REDIMAGE):
  710.             new = self.b2.copy()
  711.             cp = map(list, self.b2.cp)
  712.             i, j = self.ij
  713.             moverow = (quarkx.keydown('\022')==1)  # ALT
  714.             movecol = (quarkx.keydown('\020')==1)  # SHIFT
  715.             picked = self.b2["picked"]
  716.             if picked:
  717.                 indexes = map(lambda p,b2=self.b2:cpPos(p,b2),picked)
  718.             else:
  719.                 indexes = pointsToMove(moverow, movecol, i, j, self.h, self.w)        # tiglari, need to unswap 
  720.             td = (v2-v1)/128
  721.             for m,n in indexes:
  722.                  q = cp[m][n]
  723.                  cp[m][n]=quarkx.vect(q.x, q.y, q.z, q.s + delta.x, q.t + delta.y)
  724.             self.draghint = vtohint(delta)
  725.             new.cp = self.newcp = cp
  726.             new = [new]
  727.         else:
  728.             self.newcp = None
  729.             new = None
  730.         return [self.b2], new
  731.  
  732. #
  733. # getting tag point to actually tag the bezier control point.
  734. #
  735. def tagB2CpClick(m):
  736.     editor = mapeditor()
  737.     if editor is None: return
  738.     tagb2cp(m.o, editor)
  739.  
  740. def originmenu(self, editor, view, oldoriginmenu = quarkpy.qhandles.GenericHandle.OriginItems.im_func):
  741.   menu = oldoriginmenu(self, editor, view)
  742.   if isinstance(self, CPHandle):
  743.       for item in menu:
  744.           try:
  745.               if item.tagger:
  746.                   item.onclick = tagB2CpClick
  747.                   item.o = self
  748.           except (AttributeError):
  749.               pass
  750.   return menu
  751.   
  752. quarkpy.qhandles.GenericHandle.OriginItems = originmenu
  753.  
  754. #
  755. # Stuff that's meaningful for the whole patch should go here
  756. #
  757. def newb2menu(o, editor, oldmenu=mapentities.BezierType.menubegin.im_func):
  758.     "update for RMB menu for beziers"
  759.  
  760.  
  761.     def projtexclick(m, o=o, editor=editor):
  762.         new = o.copy()
  763.         texFromFaceToB2(new, m.tagged, editor)
  764.         undo = quarkx.action()
  765.         undo.exchange(o, new)
  766.         editor.ok(undo,"project texture from tagged")
  767.  
  768.     projtex = qmenu.item("&Project from tagged", projtexclick, "|Texture of a tagged face is projected onto the patch in a `flat' way (just like project texture from tagged face onto faces).")
  769.     tagged = gettaggedface(editor)
  770.     if tagged is None:
  771.        projtex.state=qmenu.disabled
  772.     else:
  773.        projtex.tagged = tagged
  774.  
  775.     def rotclick(m, o=o, editor=editor):
  776.         ncp = RotateCpCounter(1,o.cp)
  777.         new = o.copy()
  778.         new.cp = ncp
  779.         undo=quarkx.action()
  780.         undo.exchange(o, new)
  781.         editor.ok(undo,"Spin")
  782.         editor.invalidateviews()
  783.  
  784.     rotate = qmenu.item("Rotate",rotclick,"|`Rotates' control points without changeing patch shape\n(I'm not sure if it's useful on its own but it helps in the implementation of some things so here it is anyway.)")
  785.     
  786.     def unwarpclick(m,o=o,editor=editor):
  787.         new=o.copy()
  788.         new.cp = undistortColumns(undistortRows(o.cp))
  789.         undo=quarkx.action()
  790.         undo.exchange(o, new)
  791.         editor.ok(undo,"unwarp")
  792.         
  793.     unwarp = qmenu.item("Unwarp", unwarpclick, "|Tries to reduce texture scale changes within patch, keeping corner points the same.")
  794.     
  795.     def cornertexclick(m,o=o,editor=editor):
  796.  
  797.         class pack:
  798.             "a place to stick stuff"
  799.         pack.o=o
  800.         pack.fixed=""
  801.         
  802.         def reset(self, pack=pack):
  803.             cp = pack.o.cp
  804.             m = len(cp)-1
  805.             n = len(cp[0])-1
  806.                
  807.             one = cp[0][0].s, cp[0][0].t, cp[m][n].s, cp[m][n].t
  808.             two = cp[m][0].s, cp[0][n].t, cp[0][n].s ,cp[m][0].t
  809. #            squawk("1: %s; 2: %s"%(one, two))
  810.             if one==two:
  811.                 self.src["Corners"] = cp[0][0].s, cp[0][0].t, cp[m][n].s, cp[m][n].t, 
  812.             else:
  813.                 self.src["Corners"] = None
  814.             pack.oldcnr = self.src["Corners"]
  815.         
  816.         def setup(self, pack=pack, reset=reset):
  817.             src=self.src
  818.             cp = pack.o.cp
  819.             m = len(cp)-1
  820.             n = len(cp[0])-1
  821.             src["Corner1"]=cp[0][0].s, cp[0][0].t
  822.             src["Corner2"]=cp[0][n].s, cp[0][n].t
  823.             src["Corner3"]=cp[m][0].s, cp[m][0].t
  824.             src["Corner4"]=cp[m][n].s, cp[m][n].t
  825.             reset(self)
  826.         
  827.         def action(self, pack=pack, reset=reset):
  828.             src = self.src
  829.             new = pack.o.copy()
  830.             cp = listCp(new.cp)
  831.             m = len(cp)-1
  832.             n = len(cp[0])-1
  833.             st = range(4)
  834.             if src["Corners"]!=pack.oldcnr:
  835.                 cnr = src["Corners"]
  836.                 src["Corner1"] = cnr[0], cnr[1]
  837.                 src["Corner2"] = cnr[2], cnr[1]
  838.                 src["Corner3"] = cnr[0], cnr[3]
  839.                 src["Corner4"] = cnr[2], cnr[3]
  840.             st[0]= src["Corner1"]
  841.             st[1]= src["Corner2"]
  842.             st[2]= src["Corner3"]
  843.             st[3]= src["Corner4"]
  844.             cnrs = range(4)
  845.             for (i, j, k) in ((0,0,0),(0,n,1),(m,0,2),(m,n,3)):
  846.                 cp[i][j]=quarkx.vect(cp[i][j].xyz+st[k])
  847.                 cnrs[k] = cp[i][j]
  848. #            if src["fixed"]:
  849.             if 0:
  850.                 cp2 = apply(interpolateGrid,cnrs+[len(cp),len(cp[0])])
  851.                 cp = texcpFromCp(cp, cp2)
  852.             else:
  853.                 cp = undistortColumns(undistortRows(cp))
  854.             new.cp=cp
  855.             undo=quarkx.action()
  856.             undo.exchange(pack.o, new)
  857.             self.editor.ok(undo,"corners")
  858.             pack.o = new
  859.             reset(self)
  860.  
  861.         CornerTexPos(quarkx.clickform,'cornertexpos',editor,setup,action)
  862.     
  863.     cornertex = qmenu.item("Position by &corners",cornertexclick,"|A dialog for positioning textures by specifying the texture coordinates of the corners of the patch")
  864.         
  865.  
  866.     old = oldmenu(o, editor)
  867.     texpop = findlabelled(old,'texpop')
  868.     
  869.     texpop.items = texpop.items + [projtex, cornertex, unwarp]
  870.  
  871.     return old+[rotate]
  872.  
  873. mapentities.BezierType.menubegin = newb2menu
  874.  
  875. #
  876. # Handle for the center of a Bezier patch.
  877. #
  878.  
  879. class CenterHandle(maphandles.CenterHandle):
  880.     "Bezier center."
  881.  
  882.     def __init__(self, pos, centerof):
  883.         ##c_x = quarkx.setupsubset(SS_MAP, "Building")["BezierCenterX"][0]
  884.         ##c_y = quarkx.setupsubset(SS_MAP, "Building")["BezierCenterY"][0]
  885.         ##pos = quarkx.vect(pos.x + c_x, pos.y+c_y, pos.z)
  886.         maphandles.CenterHandle.__init__(self, pos, centerof, 0x202020, 1)
  887.  
  888.     # tiglari
  889.     def menu(self, editor, view):
  890.  
  891.         return mapentities.CallManager("menu", self.centerof, editor)
  892.     # /tiglari
  893.     
  894. import qbaseeditor
  895. from plugins.tagging import drawsquare
  896. def pickfinishdrawing(editor, view, oldmore=qbaseeditor.BaseEditor.finishdrawing):
  897.     cv = view.canvas()
  898.     cv.pencolor = MapColor("Duplicator")
  899.     for item in editor.layout.explorer.sellist:
  900.         if item.type==":b2" and item["picked"] is not None:
  901.             cp = item.cp
  902.             for p in item["picked"]:
  903.                 i, j = cpPos(p, item)
  904.                 p1 = view.proj(cp[i][j])
  905.                 drawsquare(cv,p1,10)
  906.     oldmore(editor,view)
  907.  
  908. qbaseeditor.BaseEditor.finishdrawing = pickfinishdrawing
  909.  
  910.  
  911. # ----------- REVISION HISTORY ------------
  912. #$Log: mapbezier.py,v $
  913. #Revision 1.36  2001/06/17 21:05:27  tiglari
  914. #fix button captions
  915. #
  916. #Revision 1.35  2001/06/16 03:20:48  tiglari
  917. #add Txt="" to separators that need it
  918. #
  919. #Revision 1.34  2001/02/07 18:40:47  aiv
  920. #bezier texture vertice page started.
  921. #
  922. #Revision 1.33  2001/01/15 21:56:29  tiglari
  923. #remove useless `subdiv' menu item (old test code, methinks).
  924. #`picking' extended to drag (drag one picked CP now drags all)
  925. #
  926. #Revision 1.32  2000/12/30 05:28:19  tiglari
  927. #`pick' functions for acting on selected bezier cp's
  928. #
  929. #Revision 1.31  2000/08/23 12:13:53  tiglari
  930. #added knit edge RMB for patches; also double rows/columns
  931. #
  932. #Revision 1.30  2000/07/30 23:03:51  tiglari
  933. #align row/column to tagged edge added; glue to tagged removed,
  934. #since the one in plugins.maptagpoint already does the job.
  935. #
  936. #Revision 1.29  2000/07/29 01:12:14  alexander
  937. #fixed: copycp ->copyCp (texture coordinate pyton crash AGAIN :)
  938. #
  939. #Revision 1.28  2000/07/26 11:36:01  tiglari
  940. #menu reorganization (one texture popup)
  941. #
  942. #Revision 1.26  2000/07/24 13:00:02  tiglari
  943. #reorganization of bezier texture menu, added a new positioning item, `texture at corners'.  Also a sort of `rotation' of control points.
  944. #
  945. #Revision 1.25  2000/07/23 08:43:17  tiglari
  946. #project texture to tagged plane removed from bezier cp menu
  947. #(functionality now in project tex. from tagged for faces)
  948. #
  949. #Revision 1.24  2000/07/16 07:58:11  tiglari
  950. #bezier menu -> menubegin; mesh thinning
  951. #
  952. #Revision 1.23  2000/07/04 11:04:23  tiglari
  953. #fixed patch thicken bug (copycp->copyCp)
  954. #
  955. #Revision 1.22  2000/06/26 22:51:55  tiglari
  956. #renaming: antidistort_rows/columns->undistortRows/Colunmns,
  957. #tanaxes->tanAxes, copy/map/transposecp->copy/map/transposeCP
  958. #
  959. #Revision 1.21  2000/06/25 23:48:02  tiglari
  960. #Function Renaming & Reorganization, hope no breakage
  961. #
  962. #Revision 1.20  2000/06/14 21:19:39  tiglari
  963. #texture coord entry dialog fixes, drag hint shows texture coords when texture is dragged
  964. #
  965. #Revision 1.19  2000/05/29 21:43:08  tiglari
  966. #Project texture to tagged added
  967. #
  968. #Revision 1.18  2000/05/26 23:12:34  tiglari
  969. #More patch manipulation facilities
  970. #
  971. #Revision 1.17  2000/05/19 10:08:09  tiglari
  972. #Added texture projection, redid some bezier utilties
  973. #
  974. #Revision 1.16  2000/05/08 11:12:19  tiglari
  975. #fixed problems with keys for bezier cp movement
  976. #
  977.  
  978.